/*
 * 客户端实现类。
 * Copyright (C) 2009  Xubin
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.sysmo.client;

import org.sysmo.client.item.Item;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.util.HashMap;
import java.util.Timer;
import java.util.TimerTask;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.sysmo.communication.CommunicationInterface;
import org.sysmo.communication.CommunicationProtocol;
import org.w3c.dom.*;
import org.xml.sax.SAXException;

/**
 *客户端类实现.用户登录一台客户端时, 客户端读取本地配置文件. 配置文件中保存客户端ID,
 * 客户端名字; 服务器地址, 端口以及需要监控的项(Item), 对应的监控项ID和对应数据采集
 * 间隔. 读取过程中客户端为每个监控项创建一个监控项类实例, 然后调用Item类的start()方
 * 法. 读取完毕后客户端尝试连接服务器, 建立CommunicationInterface实例, 然后调用
 * serverListener()方法监听服务器消息.
 * @author Xubin
 */
public class Client {

    private String clientID;
    private String clientName;
    private HashMap<Integer, Item> itemMap;

    private InetAddress serverAddress;
    private int serverPort;
    private String signFile;
    private boolean serverConnected;
    private CommunicationInterface communication;

    private boolean shutDownFlag;


    /**
     *构造函数, 启动客户端通过外部调用start()方法实现
     *
     */
    public  Client(){
        clientID = "0";
        clientName = "0";
        serverConnected = false;
        shutDownFlag = false;
        itemMap = new HashMap<Integer, Item>();
    }

    private void readConfig() throws ParserConfigurationException, SAXException, IOException{
        ServerSettings ss = new ServerSettings("ClientConfig.xml");
        clientID = ss.settingMap.get("/clientID");
        clientName = ss.settingMap.get("/clientName");
        serverAddress = InetAddress.getByName(ss.settingMap.get("/serverAddress"));
        serverPort = Integer.parseInt(ss.settingMap.get("/serverPort"));
        signFile = ss.settingMap.get("/signFile");
        communication = new CommunicationInterface(signFile, serverAddress, serverPort);

        File f = new File("ClientConfig.xml");
        Document ClientConfig = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(f);
        NodeList items = ClientConfig.getElementsByTagName("item");
        Node node = items.item(0);
        int tempID = 0;
        String postfix = "bat";
        String commandLine = "";
        for (int i = 0; node != null; node = items.item(++i)){
            tempID = Integer.parseInt(node.getAttributes().getNamedItem("itemID").getNodeValue());
            postfix = node.getAttributes().getNamedItem("postfix").getNodeValue();
            commandLine = node.getAttributes().getNamedItem("commandLine").getNodeValue();
            Item item = new Item(tempID, communication, false);
            item.setCollectScript(tempID, postfix, 0);
            item.setCommandLine(commandLine);
            itemMap.put(tempID, item);
            item.setPeriod(Integer.parseInt(node.getAttributes().getNamedItem("period").getNodeValue()));
            item.start();
        }
    }

    private void saveConfig() throws ParserConfigurationException, SAXException, IOException{
       File f = new File("ClientConfig.xml");
       FileOutputStream fos = new FileOutputStream(f);
       fos.write(("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n").getBytes("UTF-8"));
       fos.write(("<ClientConfig>\n").getBytes("UTF-8"));
       fos.write(("\t<clientID>" + clientID + "</clientID>\n").getBytes("UTF-8"));
       fos.write(("\t<clientName>" + clientName + "</clientName>\n").getBytes("UTF-8"));
       fos.write(("\t<serverAddress>" + serverAddress.getHostAddress() + "</serverAddress>\n").getBytes("UTF-8"));
       fos.write(("\t<serverPort>" + serverPort + "</serverPort>\n").getBytes("UTF-8"));
       fos.write(("\t<signFile>" + signFile + "</signFile>\n").getBytes("UTF-8"));

       fos.write(("\t<items>\n").getBytes("UTF-8"));
       for(Item item : itemMap.values()) {
           String[] postfix = item.getCollectScript().split("\\.");
           fos.write(("\t\t<item itemID=\"" + item.getItemID() + "\" period=\"" + item.getPeriod() + "\" postfix=\"" +postfix[2]+ "\" commandLine=\"" +item.getCommandLine()+"\"/>\n").getBytes("UTF-8"));
       }
       fos.write(("\t</items>\n").getBytes("UTF-8"));
       fos.write(("</ClientConfig>").getBytes("UTF-8"));
       fos.close();
    }

    private void connectServer() throws Exception{
        serverConnected = communication.tryConnect();
        if(serverConnected) {
            communication.sendString(CommunicationProtocol.encapsulate(0, "<clientID>" + clientID + "</clientID>"));
            serverListener();
        }
        else {
            final Timer t = new Timer();
            TimerTask tt = new TimerTask() {
                public void run(){
                    if (communication.tryConnect()) {
                        try {
                            serverConnected = true;
                            communication.sendString(CommunicationProtocol.encapsulate(0, "<clientID>" + clientID + "</clientID>"));
                            t.cancel();
                            serverListener();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            };
            t.schedule(tt, 10*1000, 10*1000);
        }
    }


    /**
     *serverListener()调用通讯接口的getString()方法, 等待服务器发送消息, 收到消息
     * 后, 解析消息类型, 然后根据类型调用相关方法, 如添加删除监控项等操作, 执行完这些
     * 方法后会继续调用这个serverListener()方法
     */
    private void serverListener() throws Exception{
        String message;
        int messageType;
        int itemID = 0;
        int withFile;
        while(!shutDownFlag && serverConnected){
            try{
                message = communication.getString();
                Document messageDoc = CommunicationProtocol.getDocumentFromString(message);
                messageType = CommunicationProtocol.getType(messageDoc);
                switch(messageType){
                    case 1:
                        clientID = messageDoc.getElementsByTagName("clientID").item(0).getFirstChild().getNodeValue();
                        communication.sendString("aa");
                        saveConfig();
                        break;
                    case 3:
                        communication.closeConnection();
                        serverConnected = false;
                        Thread.sleep(60*1000);
                        connectServer();
                        break;
                    case 4:
                        shutDown();
                        removeMe();
                        break;
                    case 5:
                        addItem(Integer.parseInt(messageDoc.getElementsByTagName("itemID").item(0).getFirstChild().getNodeValue()));
                        break;
                    case 6: case 7: case 8: case 9: case 10: case 13: case 15: case 16: case 17:
                        itemID = Integer.parseInt(messageDoc.getElementsByTagName("itemID").item(0).getFirstChild().getNodeValue());
                        if(!itemMap.containsKey(itemID)){
                            communication.sendString(CommunicationProtocol.encapsulateWithItemID(17, itemID, ""));
                        }
                        else switch (messageType){
                            case 6:
                                setItemPeriod(itemID, Integer.parseInt(messageDoc.getElementsByTagName("timeInterval").item(0).getFirstChild().getNodeValue()));
                                saveConfig();
                                break;
                            case 7:
                                itemMap.get(itemID).setCommandLine(messageDoc.getElementsByTagName("commandLine").item(0).getFirstChild().getNodeValue());
                                String postfix = messageDoc.getElementsByTagName("postfix").item(0).getFirstChild().getNodeValue();
                                withFile = Integer.parseInt(messageDoc.getElementsByTagName("withFile").item(0).getFirstChild().getNodeValue());

                                itemMap.get(itemID).setCollectScript(itemID, postfix, withFile);
                                saveConfig();
                                break;
                            case 8:
                                //itemID = Integer.parseInt(messageDoc.getElementsByTagName("itemID").item(0).getFirstChild().getNodeValue());
                                itemMap.get(itemID).setTestScript(messageDoc.getElementsByTagName("script").item(0).getFirstChild().getNodeValue());
                                break;
                            case 9:
                                //itemID = Integer.parseInt(messageDoc.getElementsByTagName("itemID").item(0).getFirstChild().getNodeValue());
                                withFile = Integer.parseInt(messageDoc.getElementsByTagName("withFile").item(0).getFirstChild().getNodeValue());

                                itemMap.get(itemID).sendCollectScript(withFile);
                                break;
                            case 10:
                                //itemID = Integer.parseInt(messageDoc.getElementsByTagName("itemID").item(0).getFirstChild().getNodeValue());
                                itemMap.get(itemID).sendTestScript();
                                break;
                            case 13:
                                //itemID = Integer.parseInt(messageDoc.getElementsByTagName("itemID").item(0).getFirstChild().getNodeValue());
                                deleteItem(itemID);
                                break;
                            case 15:
                                //itemID = Integer.parseInt(messageDoc.getElementsByTagName("itemID").item(0).getFirstChild().getNodeValue());
                                itemMap.get(itemID).pause();
                                break;
                            case 16:
                                //itemID = Integer.parseInt(messageDoc.getElementsByTagName("itemID").item(0).getFirstChild().getNodeValue());
                                itemMap.get(itemID).start();
                                break;
                            case 17:
                                deleteItem(itemID);
                                break;
                            default: break;
                        }
                        break;
                    default: break;
                }
            }catch (Exception e){
                communication.closeConnection();
                connectServer();

            }
        }
    }

    /**
     * 关闭客户端时, 将shutDownFlag置为false, 停止所有Item监控任务, 并且关闭连接.
     */
    public void shutDown() throws IOException{
        shutDownFlag = true;
        for(Item item : itemMap.values()){
            item.pause();
        }
        communication.sendString(CommunicationProtocol.encapsulate(2, ""));
        communication.closeConnection();
    }

    /**
     * 启动客户端, 直接读取配置文件
     * @throws javax.xml.parsers.ParserConfigurationException
     * @throws org.xml.sax.SAXException
     * @throws java.io.IOException
     */
    public void start() throws Exception{
        readConfig();
        connectServer();
    }

    private void addItem(Integer itemID) throws ParserConfigurationException, SAXException, IOException{
        Item newItem = new Item(itemID, communication, true);
        itemMap.put(itemID, newItem);
        saveConfig();
    }

    private void deleteItem(Integer itemID) throws ParserConfigurationException, SAXException, SAXException, IOException{
        itemMap.get(itemID).delete();
        itemMap.remove(itemID);
        saveConfig();
    }

    private void setItemPeriod(Integer itemID, long mPeriod){
        itemMap.get(itemID).setPeriod(mPeriod);
    }

    private void removeMe() throws ParserConfigurationException, SAXException, IOException {
        for (Item item : itemMap.values()) {
            item.delete();
        }
        itemMap.clear();
        clientID = "0";
        saveConfig();
    }
}
